# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Name:        tests.py
# Purpose:     GAEDrive Client: tests of net_io, dir/data cache, and FS
#
# Author:      Crane Chu <cranechu (at) gmail.org>
#
# Created:     Nov. 20, 2009
# Copyright:   Copyright 2009,2010 Crane Chu
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#-------------------------------------------------------------------------------
#!/usr/bin/env python

__author__ = 'Crane Chu'


# MODULES
import unittest
import os
import time
import urllib2
import net_io
import dir_cache


class NetIOTest(unittest.TestCase):
    def setUp(self):
        # format GAEDrive: master and chunk server
        self.net_io = net_io.NetIO()
        self.net_io.master.Send("/format", passwd = 'automation')
        urllib2.urlopen("http://localhost:8081/format")

    def testMater(self):
        self.net_io = net_io.NetIO()
        self.assert_(self.net_io)
        ret = self.net_io.rpc_master("statfs", '/')
        self.assertEqual(ret['f_bsize'], gaedrive_config.CHUNK_SIZE)
        self.assert_(ret)

    def testChunk(self):
        self.net_io = net_io.NetIO()
        self.assert_(self.net_io)
        
        ret = self.net_io.rpc_master("create", "/test", 0x755)
        self.assert_(ret, ret)
        fh = self.net_io.rpc_master("open", "/test", os.O_WRONLY)
        self.assert_(ret, fh)
        chunks, now, statfs = \
                self.net_io.rpc_master("write", "/test", 0, 1, fh); now; statfs
        self.assert_(chunks)
        chunk = chunks[0]
        ret = self.net_io.writeChunk(chunk[0], chunk[1], '1', fh)
        self.assert_(ret)
        
        chunk_data = self.net_io.readChunk(chunk[0], chunk[1], fh)
        self.assertEqual(chunk_data, '1')
        
        chunks, now, statfs = \
                self.net_io.rpc_master("write", "/test", 0, 5, fh); now
        self.assert_(chunks)
        chunk = chunks[0]
        ret = self.net_io.writeChunk(chunk[0], chunk[1], b'23456', fh)
        self.assert_(ret)

        chunk_data = self.net_io.readChunk(chunk[0], chunk[1], fh)
        self.assertEqual(chunk_data, '23456')
        
        ret = self.net_io.rpc_master("release", "/test", 5, now, fh)
        self.assert_(ret)
        ret = self.net_io.rpc_master("unlink", "/test")
        self.assert_(ret)

class DIRCacheTest(unittest.TestCase):
    def setUp(self):
        # format GAEDrive: master and chunk server
        self.net_io = net_io.NetIO()
        self.net_io.master.Send("/format", passwd = 'automation')
        urllib2.urlopen("http://localhost:8081/format")

    def testDIR(self):
        self.net_io = net_io.NetIO()
        self.assert_(self.net_io)
        self.dir_cache = dir_cache.DIRCache(self.net_io)
        self.dir_cache.readdir('/')
        self.dir_cache.mkdir('/dir1', 0x755)
        self.dir_cache.create('/dir1/file1', 0x755)
        self.dir_cache.readdir('/')
        self.dir_cache.readdir('/dir1')
        self.dir_cache.create('/dir1/file2', 0x755)
        self.dir_cache.unlink('/dir1/file1')
        self.dir_cache.readdir('/dir1')
        self.dir_cache.mkdir('/dir2', 0x755)
        self.dir_cache.rename('/dir1/file2', '/dir2/file2')
        self.dir_cache.readdir('/dir1')
        self.dir_cache.readdir('/dir2')
        self.dir_cache.readdir('/')
        self.dir_cache.readdir('')
        self.dir_cache.create('/dir3', 0x644)
        self.dir_cache.create('/file3', 0x755)
        self.dir_cache.create('/dir3/file1', 0x755)
        self.dir_cache.rename('/dir2/file2', '/file3')
        self.dir_cache.chmod('/file3', 0x777)
        self.dir_cache.chown('/file3', 8, 8)
        self.dir_cache.open('/file3', os.O_WRONLY)
        self.dir_cache.getattr('/file3')
        self.dir_cache.rmdir('/dir2')
        self.dir_cache.utimens('/file3', (time.time(), time.time()))
        self.dir_cache.getxattr('/file3', 'secutiry')
        self.dir_cache.setxattr('/file3', 'name', 'crane')
        self.dir_cache.listxattr('/file3')
        self.dir_cache.getxattr('/file3', 'name')
        self.dir_cache.removexattr('/file3', 'name')
        self.dir_cache.listxattr('/file3')
        self.dir_cache.unlink('/file3')
        self.dir_cache.rmdir('/dir1')


#####################################################################
######
######    test cases on real file operations (w/o fuse.py!!!)
######
#####################################################################
import logging
import gaedrive_config
from client_main import GAEDriveClient
from stat import S_IFDIR, S_IFLNK, S_IFREG

CACHE_PATH = '/home/cranechu/GAE/GAEDrive/client/SDCache'

# here we do the system test within the framework of unittest
class GAEDriveSystemTest(unittest.TestCase):
    def setUp(self):
        # format GAEDrive: master and chunk server
        self.net_io = net_io.NetIO()
        self.net_io.master.Send("/format", passwd = 'automation')
        urllib2.urlopen("http://localhost:8081/format")
        
        # init the client_main
        self.g = GAEDriveClient('/home/cranechu/GAE/GAEDrive/client/mnt')
        assert self.g
    
    def tearDown(self):
        # clear data cache
        write_dir = CACHE_PATH + '/.write/'
        read_dir = CACHE_PATH + '/.read/'
        for dir in (write_dir, read_dir):
            files = os.listdir(dir)
            for file in files:
                os.unlink(dir + file)
            os.rmdir(dir)
            os.mkdir(dir)
        
        # clear dir cache and shutdown
        del self.g
    
    def disable_something(self):
        if self.disable_mode is 'none':
            self.net_io.disable = False
        elif self.disable_mode is 'net_io':
            self.net_io.disable = True
        elif self.disable_mode is 'cache':
            # clear the data_cache
            self.net_io.disable = False
            self.g.clear()
        else:
            print self.disable_mode
            assert False


class BasicFileOperationTest(GAEDriveSystemTest):
    testsuite = []

    testsuite.append("testCreateOneFile")
    def testCreateOneFile(self):
        filename = '/file1'
        mode = 0x755
        fd = self.g.create(filename, mode)
        self.assertNotEqual(fd, 0, "creating file failed")
        ret = self.g.release(filename, fd); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/', 0)
        self.assertEqual(fl, ['.', '..', 'file1'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # read empty file
        data = self.g.read(filename, 1024, 0, fd)
        self.assertEqual(data, '', data)
        
    testsuite.append("testCreateTwoFiles")
    def testCreateTwoFiles(self):        
        filename1 = '/file1'
        filename2 = '/file2'
        mode = 0x755
        fd1 = self.g.create(filename1, mode)
        self.assertNotEqual(fd1, 0, "creating file1 failed")
        fd2 = self.g.create(filename2, mode)
        self.assertNotEqual(fd2, 0, "creating file2 failed")
        ret = self.g.release(filename1, fd1); ret
        ret = self.g.release(filename2, fd2); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'file1', 'file2'], 'files not exist')
        st = self.g.getattr(filename1)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        st = self.g.getattr(filename2)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
    
    testsuite.append("testCreateEmptyDir")
    def testCreateEmptyDir(self):
        dirname = '/dir1'
        mode = 0x666
        fd = self.g.mkdir(dirname, mode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        
        self.disable_something()

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], fl)
        st = self.g.getattr(dirname)
        self.assertEqual(st["st_mode"], S_IFDIR | mode, st["st_mode"])
        
        
    testsuite.append("testCreateDirAndItsFile")
    def testCreateDirAndItsFile(self):
        dirname = '/dir1'
        filename = '/dir1/file1'
        dirmode = 0x666
        filemode = 0x755
        fd = self.g.mkdir(dirname, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        
        self.disable_something()

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], 'dir not exists')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        # check the file
        fl = self.g.readdir(dirname)
        self.assertEqual(fl, ['.', '..', 'file1'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
    testsuite.append("testCreateDirAndAnotherFile")
    def testCreateDirAndAnotherFile(self):
        dirname = '/dir1'
        filename = '/file1'
        dirmode = 0x666
        filemode = 0x755
        fd = self.g.mkdir(dirname, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        
        self.disable_something()

        # check the dir and file
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1', 'file1'], 'dir or file not exists')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
    testsuite.append("testCreateSymbolLink")
    def testCreateSymbolLink(self):
        filename = '/file1'
        linkname = '/link1'
        filemode = 0755
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        # create the symbol link
        self.g.symlink(linkname, filename)
        
        self.disable_something()
        
        # check the symbol link
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1', 'link1']), fl)
        st = self.g.getattr(linkname)
        self.assertEqual(st["st_mode"], S_IFLNK | 0777, st["st_mode"])
        target = self.g.readlink(linkname)

    testsuite.append("testDeleteSymbolLink")
    def testDeleteSymbolLink(self):
        filename = '/file1'
        linkname = '/link1'
        filemode = 0755
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        # create the symbol link
        self.g.symlink(linkname, filename)
        # check the symbol link
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1', 'link1']), fl)
        st = self.g.getattr(linkname)
        self.assertEqual(st["st_mode"], S_IFLNK | 0777, st["st_mode"])
        target = self.g.readlink(linkname)

        # delete the symbol link
        self.g.unlink(linkname)
        
        self.disable_something()
        
        # check the symbol link
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), fl)

    testsuite.append("testCreateChineseNameFile")
    def testCreateChineseNameFile(self):
        dirname = '/目录a'
        filename = '/目录a/文件1'
        dirmode = 0x666
        filemode = 0x755
        fd = self.g.mkdir(dirname, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        
        self.disable_something()

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', '目录a'], 'dir not exists')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        # check the file
        fl = self.g.readdir(dirname)
        self.assertEqual(fl, ['.', '..', '文件1'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')

    testsuite.append("testCreateSpecialNameFile")
    def testCreateSpecialNameFile(self):
        dirname = '/a b'
        filename = '/a b/c~d'
        dirmode = 0x666
        filemode = 0x755
        fd = self.g.mkdir(dirname, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        
        self.disable_something()

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'a b'], 'dir not exists')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        # check the file
        fl = self.g.readdir(dirname)
        self.assertEqual(fl, ['.', '..', 'c~d'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
    testsuite.append("testChangeMode")
    def testChangeMode(self):
        filename = '/file1'
        filemode = 0755
        newmode = 0666
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertEqual(st["st_mode"], S_IFREG | filemode, st["st_mode"])
        # change the mod
        self.g.chmod(filename, newmode)
        
        self.disable_something()
        
        # check the file again
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertEqual(st["st_mode"], S_IFREG | newmode, 'wrong file mode after changing')
        
    testsuite.append("testChangeOwner")
    def testChangeOwner(self):
        filename = '/file1'
        filemode = 0755
        newuid = 1234
        newgid = 5678
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')

        # change the mod
        self.g.chown(filename, newuid, newgid)
        
        self.disable_something()
        
        # check the file again
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        import fuse; uid, gid, pid = fuse.fuse_get_context(); pid
        self.assertEqual(st["st_uid"], uid)
        self.assertEqual(st["st_gid"], gid)
        
    testsuite.append("testChangeUTimens")
    def testChangeUTimens(self):
        filename = '/file1'
        filemode = 0755
        orig_time = time.time()
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')

        # change the times
        now_time = time.time()
        self.g.utimens(filename)
        
        self.disable_something()
        
        # check the file again
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertAlmostEqual(st["st_ctime"] / 100, orig_time / 100, 1, 'wrong creat time')
        self.assertAlmostEqual(st["st_atime"] / 100, now_time / 100, 1, 'wrong gid')

    testsuite.append("testXAttrs")
    def testXAttrs(self):
        filename = '/file1'
        filemode = 0755
        fd1 = self.g.create(filename, filemode)
        self.assertNotEqual(fd1, 0, "creating file failed")
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')

        # test xattr APIs
        name1 = 'first name'
        value1 = 'Crane'
        value11 = 'He'
        name2 = 'last name'
        value2 = 'Chu'
        # set name1
        x = self.g.listxattr(filename)
        self.assertEqual(x, [], 'wrong init xattrs')
        ret = self.g.setxattr(filename, name1, value1); ret
        # check the name1 value
        value = self.g.getxattr(filename, name1)
        self.assertEqual(value, value1, 'setxattr wrong')
        
        # set name1 again
        x = self.g.listxattr(filename)
        self.assertEqual(x, [name1], 'wrong init xattrs')
        ret = self.g.setxattr(filename, name1, value11); ret
        
        self.disable_something()
        
        # check the xattr
        value = self.g.getxattr(filename, name1)
        self.assertEqual(value, value11, 'setxattr wrong')
        x = self.g.listxattr(filename)
        self.assertEqual(x, [name1], 'wrong init xattrs')
        
        # set name2
        ret = self.g.setxattr(filename, name2, value2); ret
        
        self.disable_something()
        
        # check the xattr
        value = self.g.getxattr(filename, name2)
        self.assertEqual(value, value2, 'setxattr wrong')
        value = self.g.getxattr(filename, name1)
        self.assertEqual(value, value11, 'setxattr wrong')
        x = self.g.listxattr(filename)
        self.assertEqual(set(x), set([name1, name2]), 'wrong xattrs')
        
        # remove name1
        ret = self.g.removexattr(filename, name1); ret
        
        self.disable_something()
        
        # check the xattr
        value = self.g.getxattr(filename, name1)
        self.assertFalse(value, 'remove xattr wrong')
        x = self.g.listxattr(filename)
        self.assertEqual(set(x), set([name2]), 'wrong xattrs')

    testsuite.append("testStatFS")
    def testStatFS(self):
        dirname = '/dir1'
        mode = 0666
        fd = self.g.mkdir(dirname, mode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        
        self.disable_something()

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], 'dir not exists')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        
        # statfs
        ret = self.g.statfs('/')
        self.assertEqual(ret['f_bsize'], gaedrive_config.CHUNK_SIZE)
        ret = self.g.statfs(dirname)
        self.assertEqual(ret['f_files'], 0)
        
        
class BasicDIROperationTest(GAEDriveSystemTest):
    testsuite = []

    testsuite.append("testRemoveOneFile")
    def testRemoveOneFile(self):
        filename1 = '/file1'
        filemode = 0755
        fd = self.g.create(filename1, filemode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        self.g.release(filename1, fd)
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'create fail')
        # remove it
        ret = self.g.unlink(filename1); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..']), fl)
        
    testsuite.append("testRemoveOneEmptyDir")
    def testRemoveOneEmptyDir(self):
        dirname = '/dir1'
        mode = 0666
        fd = self.g.mkdir(dirname, mode)
        self.assertNotEqual(fd, 0, "creating dir failed")

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], 'dir not exists')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        
        # remove the dir
        ret = self.g.rmdir(dirname); ret
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..'], 'dir is not removed')
        
    testsuite.append("testRemoveNonEmptyDir")
    def testRemoveNonEmptyDir(self):
        dirname1 = '/dir1'
        dirname2 = '/dir1/dir2'
        filename = '/dir1/file'
        dirmode = 0666
        filemode = 0755
        fd = self.g.mkdir(dirname1, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.mkdir(dirname2, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.create(filename, filemode)
        self.assertNotEqual(fd, 0, "creating dir failed")

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], 'dir not exists')
        st = self.g.getattr(dirname1)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        fl = self.g.readdir(dirname1)
        self.assertEqual(set(fl), set(['.', '..', 'dir2', 'file']), 'create fail')
        st = self.g.getattr(dirname2)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')

        self.disable_something()
        
        # remove the dir
        ret = self.g.rmdir(dirname1); ret
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], fl)
        self.g.getattr(dirname2)

        # close file, remove the dir
        self.g.release(filename, fd)
        ret = self.g.rmdir(dirname1); ret
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..'], fl)
        self.assertRaises(OSError, self.g.getattr, dirname2)
        
    testsuite.append("testRenameOneFile")
    def testRenameOneFile(self):
        filename1 = '/file1'
        filename2 = '/file2'
        filemode = 0755
        fd = self.g.create(filename1, filemode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'create fail')

        # close and rename
        ret = self.g.release(filename1, fd); ret
        ret = self.g.rename(filename1, filename2); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file2']), fl)

    testsuite.append("testRenameOneEmptyDir")
    def testRenameOneEmptyDir(self):
        dirname = '/dir1'
        newdirname = '/dir2'
        mode = 0666
        fd = self.g.mkdir(dirname, mode)
        self.assertNotEqual(fd, 0, "creating dir failed")

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], 'dir not exists')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        
        # rename the dir
        ret = self.g.rename(dirname, newdirname); ret
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir2'], 'dir is not renamed')
        
    testsuite.append("testRenameOneNonEmptyDir")
    def testRenameOneNonEmptyDir(self):
        dirname1 = '/dir1'
        newdirname = '/newdir1'
        dirname2 = '/dir1/dir2'
        filename = '/dir1/file'
        dirmode = 0666
        filemode = 0755
        fd = self.g.mkdir(dirname1, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.mkdir(dirname2, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.create(filename, filemode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        self.g.release(filename, fd)

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], 'dir not exists')
        st = self.g.getattr(dirname1)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        fl = self.g.readdir(dirname1)
        self.assertEqual(set(fl), set(['.', '..', 'dir2', 'file']), 'create fail')
        st = self.g.getattr(dirname2)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # remove the dir
        ret = self.g.rename(dirname1, newdirname); ret
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'newdir1'], fl)
        fl = self.g.readdir(newdirname)
        self.assertEqual(set(fl), set(['.', '..', 'dir2', 'file']), 'rename fail')
        
    testsuite.append("testMoveOneFile")
    def testMoveOneFile(self):
        dirname = '/dir1'
        filename1 = '/file1'
        filename2 = '/dir1/file1'
        dirmode = 0666
        filemode = 0755
        fd = self.g.mkdir(dirname, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.create(filename1, filemode)
        self.assertNotEqual(fd, 0, "creating file failed")
        self.g.release(filename1, fd)

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'dir1', 'file1']), 'create fail')
        st = self.g.getattr(dirname)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        fl = self.g.readdir(dirname)
        self.assertEqual(set(fl), set(['.', '..']), 'check dir fail')
        st = self.g.getattr(filename1)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # move the file
        ret = self.g.rename(filename1, filename2); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir1'], fl)
        fl = self.g.readdir(dirname)
        self.assertEqual(set(fl), set(['.', '..', 'file1']), 'not move here')

    testsuite.append("testMoveOneEmptyDir")
    def testMoveOneEmptyDir(self):
        dirname1 = '/dir1'
        dirname2 = '/dir2'
        newdirname = '/dir2/dir1'
        mode = 0666
        fd = self.g.mkdir(dirname1, mode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.mkdir(dirname2, mode)
        self.assertNotEqual(fd, 0, "creating dir failed")

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'dir1', 'dir2']), 'dir not exists')
        st = self.g.getattr(dirname1)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        st = self.g.getattr(dirname2)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        
        # move the dir
        ret = self.g.rename(dirname1, newdirname); ret
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir2'], fl)
        fl = self.g.readdir(dirname2)
        self.assertEqual(fl, ['.', '..', 'dir1'], fl)
        
    testsuite.append("testMoveOneNonEmptyDir")
    def testMoveOneNonEmptyDir(self):
        dirname1 = '/dir1'
        filename = '/dir1/file'
        dirname12 = '/dir1/dir2'
        dirname2 = '/dir2'
        newdirname = '/dir2/dir1'
        dirmode = 0666
        filemode = 0755
        fd = self.g.mkdir(dirname1, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.mkdir(dirname2, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.mkdir(dirname12, dirmode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        fd = self.g.create(filename, filemode)
        self.assertNotEqual(fd, 0, "creating dir failed")
        self.g.release(filename, fd)

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'dir1', 'dir2']), fl)
        st = self.g.getattr(dirname1)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        fl = self.g.readdir(dirname1)
        self.assertEqual(set(fl), set(['.', '..', 'dir2', 'file']), fl)
        st = self.g.getattr(dirname12)
        self.assert_(st["st_mode"] & S_IFDIR, 'it is not a dir')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # move the dir
        ret = self.g.rename(dirname1, newdirname); ret
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'dir2'], fl)
        fl = self.g.readdir(dirname2)
        self.assertEqual(set(fl), set(['.', '..', 'dir1']), 'not move here')
        fl = self.g.readdir(newdirname)
        self.assertEqual(set(fl), set(['.', '..', 'dir2', 'file']), 'not move subfiles')
        

class AbnormalOperationTest(GAEDriveSystemTest):
    testsuite = []

    testsuite.append("testAccessNonExistFile")
    def testAccessNonExistFile(self):
        self.disable_something()
        
        filename1 = '/file1'
        self.assertRaises(OSError, self.g.getattr, filename1)
        filename2 = '/dir/file'
        self.assertRaises(OSError, self.g.getattr, filename2)
        """ other API
        self.assertRaises(OSError, self.g.chmod, filename1, 0755)
        self.assertRaises(OSError, self.g.chown, filename1, 1234, 5678)
        self.assertRaises(OSError, self.g.create, filename2, 0755)
        self.assertRaises(OSError, self.g.open, filename1, os.O_WRONLY)
        self.assertRaises(OSError, self.g.rename, filename1, filename2)
        self.assertRaises(OSError, self.g.mkdir, '/dir/dir', 0666)
        self.assertRaises(OSError, self.g.utimens, filename1)
        self.assertRaises(OSError, self.g.getxattr, filename1, 'name')
        self.assertRaises(OSError, self.g.listxattr, filename1)
        self.assertRaises(OSError, self.g.setxattr, filename1, 'name', 'crane')
        self.assertRaises(OSError, self.g.removexattr, filename1, 'name')
        self.assertRaises(OSError, self.g.statfs, '/dir')
        self.assertRaises(OSError, self.g.symlink, filename1, filename2)
        self.assertRaises(OSError, self.g.readlink, filename1)
        self.assertRaises(OSError, self.g.read, filename1, 1, 0)
        self.assertRaises(OSError, self.g.write, filename1, 'a', 0)
        self.assertRaises(OSError, self.g.flush, filename1)
        self.assertRaises(OSError, self.g.release, filename1)
        self.assertRaises(OSError, self.g.truncate, filename1, 1)
        self.assertRaises(OSError, self.g.unlink, filename1)
        self.assertRaises(OSError, self.g.rmdir, '/dir')
        self.assertRaises(OSError, self.g.readdir, '/dir')
        """
        
    testsuite.append("testMoveToNonExistDIR")
    def testMoveToNonExistDIR(self):
        filename1 = '/file1'
        filename2 = '/dir1/file1'
        filemode = 0755
        fd = self.g.create(filename1, filemode)
        self.assertNotEqual(fd, 0, "creating file failed")
        self.g.release(filename1, fd)

        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(set(fl), set(['.', '..', 'file1']), fl)
        st = self.g.getattr(filename1)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # move the file
        self.assertRaises(OSError, self.g.rename, filename1, filename2)
        
        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'file1'], fl)
                         
    testsuite.append("testOpenFileTwiceForWrite")
    def testOpenFileTwiceForWrite(self):
        filename = '/file1'
        mode = 0x755
        fd = self.g.create(filename, mode)
        self.assertNotEqual(fd, 0, "creating file failed")
        ret = self.g.release(filename, fd); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/', 0)
        self.assertEqual(fl, ['.', '..', 'file1'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # open the file 1st
        fd = self.g.open(filename, os.O_WRONLY)
        self.assert_(fd)
        
        # open file file 2nd
        fd = self.g.open(filename, os.O_WRONLY)
        self.assert_(fd)
        
    testsuite.append("testDeleteWritingFile")
    def testDeleteWritingFile(self):
        filename = '/file1'
        mode = 0x755
        fd = self.g.create(filename, mode)
        self.assertNotEqual(fd, 0, "creating file failed")
        ret = self.g.release(filename, fd); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/', 0)
        self.assertEqual(fl, ['.', '..', 'file1'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # open the file 1st
        fd = self.g.open(filename, os.O_WRONLY)
        self.assert_(fd)
        
        # delete the file
        ret = self.g.unlink(filename)
        self.assertFalse(ret)

        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..'], fl)

    testsuite.append("testRenameWritingFile")
    def testRenameWritingFile(self):
        filename = '/file1'
        filename2 = '/file2'
        mode = 0x755
        fd = self.g.create(filename, mode)
        self.assertNotEqual(fd, 0, "creating file failed")
        ret = self.g.release(filename, fd); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/', 0)
        self.assertEqual(fl, ['.', '..', 'file1'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # open the file 1st
        fd = self.g.open(filename, os.O_WRONLY)
        self.assert_(fd)
        
        # delete the file
        ret = self.g.rename(filename, filename2)
        self.assertFalse(ret)

        self.disable_something()
        
        # check the dir
        fl = self.g.readdir('/')
        self.assertEqual(fl, ['.', '..', 'file2'], fl)
        
                        
class FileReadWriteTest(GAEDriveSystemTest):
    testsuite = []

    testsuite.append("testSmallReadWrite")
    def testSmallReadWrite(self):
        filename = '/file1'
        mode = 0x755
        data = '1234567890'
        fd = self.g.create(filename, mode)
        self.assertNotEqual(fd, 0, "creating file failed")
        ret = self.g.release(filename, fd); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/', 0)
        self.assertEqual(fl, ['.', '..', 'file1'], 'file not exists')
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # open the file
        fd = self.g.open(filename, os.O_WRONLY)
        self.assert_(fd)
        
        # write data
        stat = self.g.statfs('/')
        self.assertNotEqual(stat["f_bavail"], 0, stat["f_bavail"])
        bavail = stat["f_bavail"]
        n = self.g.write(filename, data, 0, fd)
        self.assertEqual(n, len(data), n)
        
        self.disable_something()
        
        # check the data
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(check_data, data, check_data)
        stat = self.g.statfs('/')
        #self.assertEqual(stat["f_bavail"], bavail - 1, (stat["f_bavail"], bavail))
        
        # write data again
        n = self.g.write(filename, data, len(data), fd)
        self.assertEqual(n, len(data), n)
        
        self.disable_something()
        
        # check the data
        check_data = self.g.read(filename, len(data) * 2, 0, fd)
        self.assertEqual(check_data, data * 2, check_data)
        
        # edit data
        n = self.g.write(filename, data, 0, fd)
        self.assertEqual(n, len(data), n)
        
        self.disable_something()
        
        # check the data
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(check_data, data, check_data)
        
        # truncate data
        ret = self.g.truncate(filename, len(data), fd); ret
        
        self.disable_something()
        
        # check the data
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, 1, len(data), fd)
        self.assertFalse(check_data)
        st = self.g.getattr(filename, fd)
        self.assertEqual(st["st_size"], len(data), st["st_size"])
        
        # close, and unlink file
        self.g.release(filename, fd)
        ret = self.g.unlink(filename); ret
        
        self.disable_something()
        
        # check the data
        self.assertRaises(OSError, self.g.read, filename, len(data), 0, fd)
        self.assertRaises(OSError, self.g.getattr, filename, fd)
                         
    testsuite.append("testLargeReadWrite")
    def testLargeReadWrite(self):
        filename = '/file1'
        mode = 0x755
        data = '1234567890ab' * 10 * 1024
        fd = self.g.create(filename, mode)
        self.assertNotEqual(fd, 0, "creating file failed")
        ret = self.g.release(filename, fd); ret
        
        self.disable_something()
        
        # check the file
        fl = self.g.readdir('/', 0)
        self.assertEqual(fl, ['.', '..', 'file1'], fl)
        st = self.g.getattr(filename)
        self.assertFalse(st["st_mode"] & S_IFDIR, 'it is not a file')
        
        # open the file
        fd = self.g.open(filename, os.O_WRONLY)
        self.assert_(fd)
        
        # write data
        stat = self.g.statfs('/')
        self.assertNotEqual(stat["f_bavail"], 0, stat["f_bavail"])
        bavail = stat["f_bavail"]; bavail
        n = self.g.write(filename, data, 0, fd)
        self.assertEqual(n, len(data), n)
        
        self.disable_something()
        
        # check the data, and the space
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(len(check_data), len(data), len(check_data))
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, 1, len(data), fd)
        self.assertFalse(check_data)
        stat = self.g.statfs('/')
        #self.assertEqual(stat["f_bavail"], bavail - 1, stat["f_bavail"])
        
        # write data again
        n = self.g.write(filename, data, len(data), fd)
        self.assertEqual(n, len(data), n)
        stat = self.g.statfs('/')
        #self.assertEqual(stat["f_bavail"], bavail - 2, (stat["f_bavail"], bavail - 2))

        self.disable_something()
        
        # check the data and the space
        check_data = self.g.read(filename, len(data) * 2, 0, fd)
        self.assertEqual(len(check_data), len(data) * 2, len(check_data))
        self.assertEqual(check_data, data * 2, check_data)
        check_data = self.g.read(filename, 1, len(data) * 2, fd)
        self.assertFalse(check_data)
        stat = self.g.statfs('/')
        #self.assertEqual(stat["f_bavail"], bavail - 2, (stat["f_bavail"], bavail - 2))

        # close it, and open, check again
        ret = self.g.release(filename, fd); ret
        fd = self.g.open(filename, os.O_RDONLY)
        self.assert_(fd)
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, len(data), len(data), fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, 1, len(data) * 2, fd)
        self.assertFalse(check_data)

        # close it, and open, check again
        ret = self.g.release(filename, fd); ret
        fd = self.g.open(filename, os.O_WRONLY)
        # edit data
        n = self.g.write(filename, data, 0, fd)
        self.assertEqual(n, len(data), n)
        
        self.disable_something()
        
        # check the data and the space
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, len(data), len(data), fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, 1, len(data) * 2, fd)
        self.assertFalse(check_data)
        stat = self.g.statfs('/')
        #self.assertEqual(stat["f_bavail"], bavail - 2, stat["f_bavail"])
        
        # close it, and check again
        ret = self.g.release(filename, fd); ret
        fd = self.g.open(filename, os.O_RDONLY)
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, len(data), len(data), fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, 1, len(data) * 2, fd)
        self.assertFalse(check_data)
        
        # truncate data in read mode
        self.g.truncate(filename, len(data), fd);
        st = self.g.getattr(filename)
        self.assertEqual(st["st_size"], len(data) * 2, st["st_size"])
        
        # truncate data in write mode
        ret = self.g.release(filename, fd); ret
        fd = self.g.open(filename, os.O_WRONLY)
        self.g.truncate(filename, len(data), fd);
        st = self.g.getattr(filename, fd)
        self.assertEqual(st["st_size"], len(data), st["st_size"])
        
        self.disable_something()
        
        # check the data and the space
        check_data = self.g.read(filename, len(data), 0, fd)
        self.assertEqual(check_data, data, check_data)
        check_data = self.g.read(filename, 1, len(data), fd)
        self.assertFalse(check_data)
        st = self.g.getattr(filename, fd)
        self.assertEqual(st["st_size"], len(data), st["st_size"])
        stat = self.g.statfs('/')
        #self.assertEqual(stat["f_bavail"], bavail - 1, stat["f_bavail"])
        
        # flush, and close the file
        ret = self.g.flush(filename); ret
        ret = self.g.release(filename, fd); ret
        
        # open and write
        fd = self.g.open(filename, os.O_WRONLY)
        for i in range(10):
            self.disable_something()
    
            # write data again
            n = self.g.write(filename, data, len(data) * i, fd)
            self.assertEqual(n, len(data), n)
            
            self.disable_something()
            
            # check the data
            check_data = self.g.read(filename, len(data) * (i + 1), 0, fd)
            self.assertEqual(len(check_data), len(data) * (i + 1), len(check_data))
            self.assertEqual(check_data, data * (i + 1), check_data)
        
        # close, and unlink file
        self.g.release(filename, fd)
        ret = self.g.unlink(filename); ret
        
        self.disable_something()
        
        # check the data
        self.assertRaises(OSError, self.g.read, filename, len(data), 0, fd)
        self.assertRaises(OSError, self.g.getattr, filename, fd)


def BATTests(unittestlog):
    # testname choices
    NONE = "NONE"; ALL = "ALL"

    
    unittestlog.info("====== file operation test w/o fuse =======")
    testname = ALL     # NONE, ALL, or specific name
    if testname is NONE:
        pass
    elif testname is ALL:
        suiteBasicFileOperation = unittest.TestSuite(
                    map(BasicFileOperationTest, BasicFileOperationTest.testsuite))
        unittest.TextTestRunner(verbosity = 1).run(suiteBasicFileOperation)
    else:   # the name-specified test case 
        unittest.TextTestRunner(verbosity = 2).run(BasicFileOperationTest(testname))


    unittestlog.info("======== directory operation test =========")
    testname = ALL     # NONE, ALL, or specific name
    if testname is NONE:
        pass
    elif testname is ALL:
        suiteBasicDIROperation = unittest.TestSuite(
                    map(BasicDIROperationTest, BasicDIROperationTest.testsuite))
        unittest.TextTestRunner(verbosity = 1).run(suiteBasicDIROperation)
    else:   # the name-specified test case 
        unittest.TextTestRunner(verbosity = 2).run(BasicDIROperationTest(testname))


    unittestlog.info("======== abnormal operation test =========")
    testname = ALL     # NONE, ALL, or specific name
    if testname is NONE:
        pass
    elif testname is ALL:
        suiteAbnormalOperation = unittest.TestSuite(
                    map(AbnormalOperationTest, AbnormalOperationTest.testsuite))
        unittest.TextTestRunner(verbosity = 1).run(suiteAbnormalOperation)
    else:   # the name-specified test case 
        unittest.TextTestRunner(verbosity = 2).run(AbnormalOperationTest(testname))

    unittestlog.info("========== file read write test ==========")
    testname = ALL      # NONE, ALL, or specific name
    if testname is NONE:
        pass
    elif testname is ALL:
        suiteFileReadWrite = unittest.TestSuite(
                    map(FileReadWriteTest, FileReadWriteTest.testsuite))
        unittest.TextTestRunner(verbosity = 1).run(suiteFileReadWrite)
    else:   # the name-specified test case 
        unittest.TextTestRunner(verbosity = 2).run(FileReadWriteTest(testname))
    

def main():
    # set up logging to file - see previous section for more details
    logging.basicConfig(level = logging.DEBUG,
                        format = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                        datefmt = '%m-%d %H:%M',
                        filename = '/home/cranechu/GAE/GAEDrive/others/unittest.log',
                        filemode = 'w')
    # define a Handler which writes INFO messages or higher to the sys.stderr
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.addFilter(logging.Filter('GAEDrive'))
    # set a format which is simpler for console use
    formatter = logging.Formatter('%(message)s')
    # tell the handler to use this format
    console.setFormatter(formatter)
    # add the handler to the root logger
    logging.getLogger('').addHandler(console)
    logging.getLogger('google').setLevel(logging.CRITICAL)    
    # logger of unit test
    unittestlog = logging.getLogger('GAEDrive.unittest')

    unittestlog.info("============= elementary tests ============")
    suiteNetIO = unittest.TestLoader().loadTestsFromTestCase(NetIOTest)
    suiteDIRCache = unittest.TestLoader().loadTestsFromTestCase(DIRCacheTest)
    elementary = unittest.TestSuite([suiteNetIO, suiteDIRCache])
    unittest.TextTestRunner(verbosity = 1).run(elementary)

    # BAT tests' five disable mode: 
    # none, net_io, dir_cache, data_cache, dir_cache & data_cache
    DISABLE_MODE = ['none', 'net_io', 'cache']
    for mode in DISABLE_MODE:
        unittestlog.info("\n\n\n<<<<<<<====== DISABLE MODE: %s =======>>>>>>>" % mode)
        # set disable mode
        GAEDriveSystemTest.disable_mode = mode
        BATTests(unittestlog)
    
    unittestlog.info("==================DONE!!!=====================")


if __name__ == '__main__':
    main()
    
